#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <getopt.h>
#include "base64_utils.h"

#define MAX_SIZE 4095
// #define log printf
#define SEND_CONST(msg) send(s_fd, msg, strlen(msg), 0);

char buf[MAX_SIZE+1];

static void recvMsg(int s_fd) {
    int r_size;
    if ((r_size = recv(s_fd, buf, MAX_SIZE, 0)) == -1)
    {
        perror("recv");
        exit(EXIT_FAILURE);
    }
    buf[r_size] = '\0'; // Do not forget the null terminator
    printf("%s", buf);
}


// receiver: mail address of the recipient
// subject: mail subject
// msg: content of mail body or path to the file containing mail body
// att_path: path to the attachment
void send_mail(const char* receiver, const char* subject, const char* msg, const char* att_path)
{
    const char* end_msg = "\r\n.\r\n";
    const char* host_name = ""; // TODO: Specify the mail server domain name
    const unsigned short port = 25; // SMTP server port
    const char* user = ""; // TODO: Specify the user
    const char* pass = ""; // TODO: Specify the password
    const char* from = ""; // TODO: Specify the mail address of the sender
    const char* temp_file = "temp";
    char dest_ip[16]; // Mail server IP address
    int s_fd; // socket file descriptor
    struct hostent *host;
    struct in_addr **addr_list;
    int i = 0;
    int r_size;

    // Get IP from domain name
    if ((host = gethostbyname(host_name)) == NULL)
    {
        herror("gethostbyname");
        exit(EXIT_FAILURE);
    }

    addr_list = (struct in_addr **) host->h_addr_list;
    while (addr_list[i] != NULL)
        ++i;
    strcpy(dest_ip, inet_ntoa(*addr_list[i-1]));

    // TODO: Create a socket, return the file descriptor to s_fd, and establish a TCP connection to the mail server
    if ((s_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        herror("create socket");
        exit(EXIT_FAILURE);
    }
    struct sockaddr_in serv_addr;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(port);
    if(inet_pton(AF_INET, dest_ip, &serv_addr.sin_addr) <= 0) {
        herror("inet_pton");
        exit(EXIT_FAILURE);
    }
    connect(s_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    // log("connect\n");

    // Print welcome message
    if ((r_size = recv(s_fd, buf, MAX_SIZE, 0)) == -1)
    {
        perror("recv");
        exit(EXIT_FAILURE);
    }
    buf[r_size] = '\0'; // Do not forget the null terminator
    printf("%s", buf);

    // Send EHLO command and print server response
    const char* EHLO = "EHLO qq.com\r\n"; // TODO: Enter EHLO command here
    send(s_fd, EHLO, strlen(EHLO), 0);
    // log("EHLO SEND\n");
    // TODO: Print server response to EHLO command
    recvMsg(s_fd);
    
    // TODO: Authentication. Server response should be printed out.
    SEND_CONST("auth login\r\n");
    recvMsg(s_fd);

    char* USERNAME = encode_str(user);
    send(s_fd, USERNAME, strlen(USERNAME), 0);
    SEND_CONST("\r\n");
    free(USERNAME);
    recvMsg(s_fd);

    char* PASSWORD = encode_str(pass);
    send(s_fd, PASSWORD, strlen(PASSWORD), 0);
    SEND_CONST("\r\n");
    free(PASSWORD);
    // log("USN PSW SEND\n");
    recvMsg(s_fd);

    // TODO: Send MAIL FROM command and print server response
    snprintf(buf, sizeof(buf), "MAIL FROM:<%s>\r\n", from);
    send(s_fd, buf, strlen(buf), 0);
    recvMsg(s_fd);
    
    // TODO: Send RCPT TO command and print server response
    snprintf(buf, sizeof(buf), "RCPT TO:<%s>\r\n", receiver);
    send(s_fd, buf, strlen(buf), 0);
    recvMsg(s_fd);
    
    // TODO: Send DATA command and print server response
    SEND_CONST("DATA\r\n");
    recvMsg(s_fd);

    // TODO: Send message data
    // send headers
    const char* boundary = "--qwertyuiop\r\n";
    snprintf(buf, sizeof(buf), "From: %s\r\n", from);
    send(s_fd, buf, strlen(buf), 0);

    snprintf(buf, sizeof(buf), "To: %s\r\n", receiver);
    send(s_fd, buf, strlen(buf), 0);

    snprintf(buf, sizeof(buf), "Subject: %s\r\n", subject);
    send(s_fd, buf, strlen(buf), 0);

    snprintf(buf, sizeof(buf), "Content-Type: multipart/mixed; boundary=qwertyuiop\r\n");
    send(s_fd, buf, strlen(buf), 0);

    SEND_CONST("MIME-Version: 1.0\r\n");

    // send attachment
    if(att_path) {
        FILE* att_file;
        if((att_file = fopen(att_path, "r")) == NULL) {
            perror("fopen attachment");
            exit(EXIT_FAILURE);
        }
        FILE* att_enc;
        if((att_enc = fopen(temp_file, "w+")) == NULL) {
            perror("fopen temp_file");
            exit(EXIT_FAILURE);
        }
        SEND_CONST(boundary);
        SEND_CONST("Content-Type: application/octet-stream\r\n");
        SEND_CONST("Content-Transfer-Encoding: base64\r\n");
        // 设置文件名
        char *p, filename[FILENAME_MAX];
        if ((p = strrchr(att_path, '/')) != NULL) // 判断文件路径中是否包含 '/'
            strcpy(filename, p+1);
        else
            strcpy(filename, att_path); // 如果文件路径中没有 '/', 直接将整个字符串复制到filename中

        snprintf(buf, sizeof(buf), "Content-Disposition: attachment; filename=\"%s\"\r\n", filename);
        send(s_fd, buf, strlen(buf), 0);

        // 发送文件
        // encode_file会莫名其妙加上回车, 所以只能去掉回车再进行发送, 这里默认Linux环境, 就不管\r\n这种情况了
        encode_file(att_file, att_enc);
        fseek(att_enc, 0, SEEK_SET);
        while (fgets(buf, MAX_SIZE, att_enc)){
            if(feof(att_enc)) break;
            buf[strlen(buf) - 1] = '\0';
            // log(buf);
            SEND_CONST(buf);
        }
        SEND_CONST("\r\n");
        fclose(att_file);
        fclose(att_enc);
        remove(temp_file);
    }

    // send message
    SEND_CONST(boundary);
    SEND_CONST("Content-Type: text/plain\r\n");
    SEND_CONST(msg);
    SEND_CONST("\r\n");
    
    // TODO: Message ends with a single period
    SEND_CONST(boundary);
    SEND_CONST("\r\n.\r\n")
    recvMsg(s_fd);

    // TODO: Send QUIT command and print server response
    const char* QUIT = "QUIT\r\n";
    send(s_fd, QUIT, strlen(QUIT), 0);
    recvMsg(s_fd);

    close(s_fd);
}

int main(int argc, char* argv[])
{
    int opt;
    char* s_arg = NULL;
    char* m_arg = NULL;
    char* a_arg = NULL;
    char* recipient = NULL;
    const char* optstring = ":s:m:a:";
    while ((opt = getopt(argc, argv, optstring)) != -1)
    {
        switch (opt)
        {
        case 's':
            s_arg = optarg;
            break;
        case 'm':
            m_arg = optarg;
            break;
        case 'a':
            a_arg = optarg;
            break;
        case ':':
            fprintf(stderr, "Option %c needs an argument.\n", optopt);
            exit(EXIT_FAILURE);
        case '?':
            fprintf(stderr, "Unknown option: %c.\n", optopt);
            exit(EXIT_FAILURE);
        default:
            fprintf(stderr, "Unknown error.\n");
            exit(EXIT_FAILURE);
        }
    }

    if (optind == argc)
    {
        fprintf(stderr, "Recipient not specified.\n");
        exit(EXIT_FAILURE);
    }
    else if (optind < argc - 1)
    {
        fprintf(stderr, "Too many arguments.\n");
        exit(EXIT_FAILURE);
    }
    else
    {
        recipient = argv[optind];
        send_mail(recipient, s_arg, m_arg, a_arg);
        exit(0);
    }
}
